/*
* Copyright 2011 Pavel Kozhin 
*
* Licensed under the Apache License, Version 2.0 (the "License"); 
* you may not use this file except in compliance with the License. 
* You may obtain a copy of the License at 
* 
* http://www.apache.org/licenses/LICENSE-2.0
* 
* Unless required by applicable law or agreed to in writing, software 
* distributed  under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
* See the License for the specific language governing permissions and 
* limitations under the License.
*
* Author: Pavel Kozhin.
*/
package org.etherframework.core.bridge
{
	import flash.errors.IllegalOperationError;
	import flash.events.EventPhase;
	import flash.system.ApplicationDomain;
	
	import org.etherframework.IEther;
	import org.etherframework.common.Access;
	import org.etherframework.common.DomainAccess;
	import org.etherframework.common.IAccessable;
	import org.etherframework.common.IDestroyable;
	import org.etherframework.common.IDetachable;
	import org.etherframework.common.command.facade.ICommandsFacade;
	import org.etherframework.common.dependency.facade.IDependenciesFacade;
	import org.etherframework.common.mediation.IMediations;
	import org.etherframework.common.mediation.facade.IMediationsFacade;
	import org.etherframework.common.mediators.facade.IMediatorsFacade;
	import org.etherframework.common.wire.IMetaProcessor;
	import org.etherframework.common.wire.IWirable;
	import org.etherframework.common.wire.IWire;
	import org.etherframework.common.wire.IWiring;
	import org.etherframework.core.IModule;
	import org.etherframework.core.context.Context;
	import org.etherframework.core.context.IContexts;
	import org.etherframework.core.context.IContextual;
	import org.etherframework.core.context.ISingleContextual;
	import org.etherframework.core.events.GetParentModuleEvent;
	import org.etherframework.core.manager.DestructionManager;
	import org.etherframework.core.module.IModulePart;
	import org.etherframework.core.module.ModuleConfig;
	import org.etherframework.core.utils.Dispatcher;
	import org.etherframework.dependency.IDependencies;
	
	[ExcludeClass]
	
	// TODO: Refactor.
	public final class BridgeFacades implements IBridgeFacades, IAccessable, IDetachable, 
		ISingleContextual, IDestroyable
	{
		private static var _mediations:IMediations;
		private var _moduleConfig:ModuleConfig;
		private var _contexts:IContexts;
		private var _module:IModule;
		private var _bridge:IBridge;
		private var _access:String;
		private var _parent:IBridgeFacades;
		private var _domain:String;
		private var _context:Context;
		private var _dependencies:IDependencies;
		private var _wiring:IWiring;
		private var _initialized:Boolean;
		private var _isGlobalAccess:Boolean;
		private var _isLocalAccess:Boolean;
		
		private var _dependenciesFacade:IDependenciesFacade;
		private var _mediatorsFacade:IMediatorsFacade;
		private var _commandsFacade:ICommandsFacade;
		private var _mediationsFacade:IMediationsFacade;
		
		public function BridgeFacades(context:Context, contexts:IContexts)
		{
			_context = context;
			_contexts = contexts;
		}
		
		public function initialize(module:IModule, bridge:IBridge, config:ModuleConfig):void
		{
			if (!module)
				throw new ArgumentError("Argument module:IModule can not be null.");
			if (!bridge)
				throw new ArgumentError("Argument bridge:IBridge can not be null.");
			
			_module = module;
			_bridge = bridge;
			_moduleConfig = config;
			_wiring = _module.wiring;
			
			if (!_mediations)
			{
				_mediations = _moduleConfig.mediations;
			}
			
			_access = _bridge.access;
			_initialized = true;
			
			access = access;
		}
		
		public function getWire(target:Object):IWire
		{
			if (!_wiring)
				return null;
			
			var result:IWire;
			
			if (_isGlobalAccess)
			{
				result = parent.getWire(target);
			}
			else
			{
				result = _wiring.hasWire(target);
				
				if (!result && parent)
				{
					result = parent.getWire(target);
				}
				
				return result || _wiring.createWire(target);
			}
			
			return result;
		}
		
		public function wire(target:IWirable, ether:IEther):IWire
		{
			if (!target)
				return null;
			
			const wire:IWire = getWire(target);
			
			if (wire)
			{
				if (!wire.ether)
					wire.initialize(ether);
				if (!wire.wired)
					wire.wire();
			}
			return wire;
		}
		
		public function unwire(target:IWirable):IWire
		{
			if (!target)
				return null;
			
			const wire:IWire = getWire(target);
			
			if (wire && wire.wired)
			{
				wire.unwire();
			}
			
			return wire;
		}
		
		public function createProcessor(metatag:String):IMetaProcessor
		{
			if (!_wiring)
				return null;
			
			var result:IMetaProcessor;
			
			if (_isGlobalAccess)
			{
				result = parent.contextual(_context).createProcessor(metatag);
			}
			else
			{
				const wiring:IWiring = _wiring.contextual(_context);
				
				if (wiring.hasProcessor(metatag))
					result = wiring.createProcessor(metatag);
				
				if (!result && parent)
					result = parent.contextual(_context).createProcessor(metatag);
			}
			
			if (!result)
			{
				result = wiring.createProcessor(metatag);
			}
			
			return result;
		}
		
		public function attach():void
		{
		}
		
		public function detach():void
		{
			_parent = null;
		}
		
		public function destroy():void
		{
			_contexts.dispose(this);
		}
		
		public function get parent():IBridgeFacades
		{
			if (_bridge.access == Access.LOCAL)
				return null;
			
			if (_parent)
				return _parent;
			
			const evt:GetParentModuleEvent = new GetParentModuleEvent(this);
			_module.view.dispatchEvent(evt);
			_parent = IBridgeFacades(evt.parent);
			
			return _parent;
		}
		
		private function getParentHandler(e:Object):void 
		{ 
			if (e.eventPhase == EventPhase.BUBBLING_PHASE)
			{
				// We should make sure that interaction happens within 
				// the same domain or related to domain hierarchy but 
				// not to the separate domain.
				if (!(e.initiator is IBridgeFacades))
					throw new IllegalOperationError("Interaction is not allowed. Probably " +
						"your module is loaded to separate ApplicationDomain.");
				
				// If application domains are not equal then only multiple 
				// domain access can accept this interaction.
				if ((e.initiator.applicationDomain != _bridge.applicationDomain) && 
					(_domain != DomainAccess.MULTIPLE))
					return;
				
				e.stopImmediatePropagation();
				e.parent = this;
			}
		}
		
		public function get access():String { return _access; }
		public function set access(value:String):void 
		{ 
			_access = value;
			_isGlobalAccess = _access == Access.GLOBAL;
			_isLocalAccess = _access == Access.LOCAL;
			
			if (_initialized)
			{
				_module.view.removeEventListener(GetParentModuleEvent.GET_PARENT, getParentHandler);
				
				if (_access == Access.HIERARCHICAL)
					_module.view.addEventListener(GetParentModuleEvent.GET_PARENT, getParentHandler);
			}
		}
		
		public function create(context:Context, type:Class):IContextual
		{
			var result:BridgeFacades = new BridgeFacades(context, _contexts);
			result.initialize(_module, _bridge, _moduleConfig);
			return result;
		}
		
		public function dispose():void
		{
			_contexts = null;
			_module.view.removeEventListener(GetParentModuleEvent.GET_PARENT, getParentHandler);
			_parent = null;
			_module = null;
			_bridge = null;
			_dependenciesFacade = null;
			_mediatorsFacade = null;
			_commandsFacade = null;
			_mediationsFacade = null;
		}
		
		public function contextual(context:Context):IBridgeFacades
		{
			return _contexts.contextual(context, IBridgeFacades, this) as IBridgeFacades;
		}
		
		public function get dependencies():IDependenciesFacade
		{ 
			if (_dependenciesFacade)
				return _dependenciesFacade;
			
			if (_module.dependencies is IModulePart)
			{
				_dependenciesFacade = IDependenciesFacade(IModulePart(_module.dependencies).getFacade(this));
			}
			return _dependenciesFacade; 
		}
		public function get mediators():IMediatorsFacade 
		{ 
			if (_mediatorsFacade)
				return _mediatorsFacade;
			
			if (_module.mediators is IModulePart)
			{
				_mediatorsFacade = IMediatorsFacade(IModulePart(_module.mediators).getFacade(this));
			}
			return _mediatorsFacade;
		}
		
		public function get commands():ICommandsFacade 
		{ 
			if (_commandsFacade)
				return _commandsFacade;
			
			if (_module.commands is IModulePart)
			{
				_commandsFacade = ICommandsFacade(IModulePart(_module.commands).getFacade(this));
			}
			return _commandsFacade;
		}
		
		public function get mediations():IMediationsFacade
		{ 
			if (_mediationsFacade)
				return _mediationsFacade;
			
			if (_mediations is IModulePart)
			{
				_mediationsFacade = IMediationsFacade(IModulePart(_mediations).getFacade(this));
			}
			return _mediationsFacade;
		}
		
		public function get context():Context { return _context; }
		public function get domain():String { return _domain; }
		public function set domain(value:String):void { _domain = value; }
		public function get applicationDomain():ApplicationDomain { return _bridge.applicationDomain; }
		public function get destructionManager():DestructionManager { return _bridge.destructionManager; }
		public function get dispatcher():Dispatcher { return _bridge.dispatcher; }
		public function get controllers():Controllers { return _bridge.controllers; }
		public function get contexts():IContexts { return _bridge.contexts; }
	}
}